package com.shujia.flink.state

import org.apache.flink.api.common.eventtime.WatermarkStrategy
import org.apache.flink.api.common.serialization.SimpleStringSchema
import org.apache.flink.connector.base.DeliveryGuarantee
import org.apache.flink.connector.kafka.sink.{KafkaRecordSerializationSchema, KafkaSink}
import org.apache.flink.connector.kafka.source.KafkaSource
import org.apache.flink.connector.kafka.source.enumerator.initializer.OffsetsInitializer
import org.apache.flink.runtime.state.hashmap.HashMapStateBackend
import org.apache.flink.streaming.api.CheckpointingMode
import org.apache.flink.streaming.api.environment.CheckpointConfig.ExternalizedCheckpointCleanup
import org.apache.flink.streaming.api.scala._

import java.util.Properties

object Demo6SinkKafkaExactlyOnce {
  def main(args: Array[String]): Unit = {
    val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment

    // 每 5000ms 开始一次 checkpoint
    env.enableCheckpointing(20000)

    // 高级选项：

    // 设置模式为精确一次 (这是默认值)
    env.getCheckpointConfig.setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE)

    // 确认 checkpoints 之间的时间会进行 500 ms
    env.getCheckpointConfig.setMinPauseBetweenCheckpoints(500)

    // Checkpoint 必须在一分钟内完成，否则就会被抛弃
    env.getCheckpointConfig.setCheckpointTimeout(60000)

    // 允许两个连续的 checkpoint 错误
    env.getCheckpointConfig.setTolerableCheckpointFailureNumber(2)

    // 同一时间只允许一个 checkpoint 进行
    env.getCheckpointConfig.setMaxConcurrentCheckpoints(1)

    // 使用 externalized checkpoints，这样 checkpoint 在作业取消后仍就会被保留
    env.getCheckpointConfig.setExternalizedCheckpointCleanup(
      ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION)


    //设置状态后端，保存状态的位置
    //HashMapStateBackend:将状态保存在内存中
    env.setStateBackend(new HashMapStateBackend())

    //状态持久化位置
    env.getCheckpointConfig.setCheckpointStorage("hdfs://master:9000/flink/checkpoint")


    val source: KafkaSource[String] = KafkaSource
      .builder[String]
      .setBootstrapServers("master:9092,node1:9092,node2:9092")
      .setTopics("exactly")
      .setGroupId("my-group")
      .setStartingOffsets(OffsetsInitializer.latest)
      .setValueOnlyDeserializer(new SimpleStringSchema())
      .build

    //使用kafka 数据源
    val linesDS: DataStream[String] = env
      .fromSource(source, WatermarkStrategy.noWatermarks(), "Kafka Source")

    /**
     * 非聚合计算
     */
    val wordsDS: DataStream[String] = linesDS.flatMap(_.split(","))

    val filterDS: DataStream[String] = wordsDS.filter(word => word != "")

    //将结果保存到kafka
    val properties = new Properties()
    //设置事务超时时间，不能打印15分钟
    properties.setProperty("transaction.timeout.ms", 1000 * 5 * 60 + "")

    val kafkaSink: KafkaSink[String] = KafkaSink
      .builder[String]()
      //kafka 集群列表
      .setBootstrapServers("master:9092,node1:9092,node2:9092")
      .setKafkaProducerConfig(properties)
      .setRecordSerializer(KafkaRecordSerializationSchema.builder()
        //指定topic
        .setTopic("words")
        //指定序列化方式
        .setValueSerializationSchema(new SimpleStringSchema())
        .build()
      )
      //指定数据处理的语义AT_LEAST_ONCE：至少一次，会出现重复数据,EXACTLY_ONCE:唯一一次
      .setDeliverGuarantee(DeliveryGuarantee.EXACTLY_ONCE)
      .build()

    filterDS.sinkTo(kafkaSink)

    env.execute()

    /**
     * 查看结果
     *  kafka-console-consumer.sh --bootstrap-server  master:9092,node1:9092,node2:9092 --isolation-level read_committed --from-beginning --topic words
     */

  }
}
